using System;
using System.Collections.Generic;
using System.IO;
using Nairo.Parser.Dom;
using System.Diagnostics;

namespace Nairo.Parser
{
	public class HTMLParser
	{
		string fileName;
		string data;
		int counter;
		List<Token> tokens;
		FileUnit currentFile;
		static FileUnit inProgressFile;
		
		public FileUnit CurrentFile {
			get { return currentFile; }
		}
		
		public HTMLParser(string fileName, Stream stream)
			: this(fileName, new StreamReader(stream).ReadToEnd())
		{
		}
		
		public HTMLParser(string fileName, string data)
		{
			this.fileName = fileName;
			this.data = data;
			this.data = this.data.Replace("\n", "");
			this.data = System.Text.RegularExpressions.Regex.Replace(this.data, @"\<\s*br\s*/>", "\n");
			this.tokens = new List<Token>();
			this.counter = 0;
			
			Nairo.Parser.Dom.TypeMap.Initialize();
		}
		
		public void Parse()
		{
			while (HasNextToken()) {
				Token current = GetNextToken();
				// Exclude empty tokens
				if (!IsWhiteSpace(current)) {
					Console.WriteLine(Trim(current).ToString());
					this.tokens.Add(current);
				}
			}
			
			this.currentFile = CreateTree(this.tokens);
			
			Console.WriteLine("\n\nCSS Styles:\n");
			
			foreach (AbstractCSSElement item in this.currentFile.Styles) {
				Console.WriteLine(item.ToString());
			}
		}
		
		public static FileUnit Parse(string data)
		{
			HTMLParser parser = new HTMLParser("empty file", data);
			parser.Parse();
			
			return parser.CurrentFile;
		}
		
		private Token Trim(Token t)
		{
			t.TokenText = t.TokenText.Trim();
			return t;
		}
		
		private FileUnit CreateTree(List<Token> tokens)
		{
			FileUnit fileUnit = new FileUnit();
			inProgressFile = fileUnit;
			
			int i = 0;
			
			while (i < tokens.Count) {
				AbstractBlock block = AnalyzeToken(tokens[i], fileUnit);
				if (IsClosingTag(tokens[i])) {
					i++;
				} else {
					if (IsOpeningTag(tokens[i])) {
						i++;
						block.Children = CreateChildren(tokens, ref i, fileUnit);
						fileUnit.Children.Add(block);
					} else {
						fileUnit.Children.Add(block);
					}
					i++;
				}
			}
			
			return fileUnit;
		}
		
		private List<INode> CreateChildren(List<Token> tokens, ref int current, INode parent)
		{
			List<INode> children = new List<INode>();
			
			while(current < tokens.Count - 1) {
				AbstractBlock block = AnalyzeToken(tokens[current], parent);
				if (IsClosingTag(tokens[current])) {
					break;
				} else {
					if (IsOpeningTag(tokens[current])) {
						current++;
						block.Children = CreateChildren(tokens, ref current, block);
						children.Add(block);
						current++;
					} else {
						children.Add(block);
						current++;
					}
				}
			}

			return children;
		}
		
		private bool IsClosingTag(Token t)
		{
			if (t.TokenText.StartsWith("</"))
				return true;
			
			return false;
		}
		
		private bool IsOpeningTag(Token t)
		{
			if ((t.TokenText.StartsWith("</")) || (t.TokenText.EndsWith("/>")))
				return false;
			if (t.TokenText.StartsWith("<%") && t.TokenText.EndsWith("%>"))
				return false;
			if (t.TokenText.StartsWith("<!--") && t.TokenText.EndsWith("-->"))
				return false;
			if (t.TokenText.ToLowerInvariant().StartsWith("<!doctype"))
				return false;
			if (t.TokenText.StartsWith("<") && t.TokenText.EndsWith(">"))
				return true;
			
			return false;
		}
		
		private AbstractBlock AnalyzeToken(Token t, INode parent)
		{
			if (t.TokenText.StartsWith("<!--") && t.TokenText.EndsWith("-->")) {
				return new CommentBlock(inProgressFile, t.TokenText, parent, null);
			} else {
				if (t.TokenText.StartsWith("<%") && t.TokenText.EndsWith("%>")) {
					return new AspNetCodeBlock(inProgressFile, t.TokenText, parent, null);
				} else {
					if (t.TokenText.StartsWith("<") && t.TokenText.EndsWith(">")) {
						if (IsClosingTag(t))
							return HtmlTag.FromHTML(t.TokenText, parent);
						else
							return HtmlTag.FromHTML(t.TokenText, parent);
					} else
						return new InnerHTMLBlock(parent.File, t.TokenText, parent, null);
				}
			}
		}
		
		private bool IsWhiteSpace(Token token)
		{
			foreach (char c in token.TokenText) {
				if (!char.IsWhiteSpace(c))
					return false;
			}
			
			return true;
		}
		
		private bool HasNextToken()
		{
			return (counter < this.data.Length);
		}
		
		private Token GetNextToken()
		{
			System.Text.StringBuilder buffer = new System.Text.StringBuilder();
			bool continuing = true;
			bool inString = false, inTag = false, innerText = false;
			int start = -1, end = -1;

			while (continuing) {
				if (counter >= this.data.Length)
					break;
				
				char c = this.data[counter];
				
				switch (c) {
					case '<':
						if (innerText) {
							end = counter;
							counter--;
							continuing = false;
							continue;
						}
						
						if (!(inString && inTag) && (start == -1)) {
							inTag = true;
							buffer.Append(c);
							start = counter;
						} else {
							end = counter;
						}
						break;
					case '>':
						buffer.Append(c);
						if (!inString && inTag) {
							end = counter;
							inTag = false;
							innerText = true;
							continuing = false;
							continue;
						}
						break;
					case '"':
						inString = !inString;
						buffer.Append(c);
						break;
					default:
						if (start == -1) {
							start = counter;
							innerText = true;
						}
						buffer.Append(c);
						break;
				}
				
				counter++;
			}
			
			counter++;
			
			return new Token(buffer.ToString(), start, end);
		}
	}
}
